/* DrawView.m  part of RatMandel 
 * draws the Mandelbrot set of rational quadratic functions 
 * Author: William Gilbert
 * Written: May 1991 and last modified Dec 1992 
 */

#import "DrawView.h"

#define PRINTED_SIZE 5.6				// size of printed window in inches
#define LINE_WIDTH_FACTOR 1.1			// fudge factor to avoid aliasing
#define INF 10e20
#define A_PLANE 0
#define A_INVERSE_PLANE 1
#define B_PLANE 2

@implementation DrawView

- appDidInit:sender
{
	[notesText setOpaque:YES];
	[notesText setMonoFont:NO];
	[notesText setFont:[Font newFont:"Ohlfs" size:11.0]];
	[self presetRing:sender];
	[self getParameters:&displayplane xr:&xr yr:&yr xs:&xs ys:&ys 
		xmin:&xmin ymin:&ymin xmax:&xmax ymax:&ymax 
		iterates:&iterates cycle:&cycle close:&close];
	[[PrintPanel new] setAccessoryView:accessoryBox];
	lineCompleted = 0;
	return self;
}

- start:sender
{
	running = YES;
	[self display];
    return self;
}

- clearNotes:sender
{
    [notesText selectAll:nil];
    [notesText delete:nil];
	return self;
}

- lastCoords:sender
{
	[self setParameters:displayplane xr:xr yr:yr xs:xs ys:ys 
		xmin:xmin ymin:ymin xmax:xmax ymax:ymax 
		iterates:iterates cycle:cycle close:close];
	[self clearNotes:sender];
  	[self lockFocus];
    PSnewinstance();
 	[self unlockFocus];
	return self;
}

- presetMandelbrot:sender
 {
    [notesText setText:"\n Mandelbrot Set\n ==============\n The critical point -1 is also fixed.\n F(z) is conjugate to\n     z -> z^2 + c\n\n This is not the usual\nview of the Mandelbrot\nset.  See Mandelbrot\nPlate 189.\n"];
	[self setParameters:A_PLANE xr:-1.0 yr:0.0 xs:2.0 ys:0.0 
		xmin:-2.0 ymin:-3.0 xmax:4.0 ymax:3.0  
		iterates:50 cycle:1 close:0.01];
	[self parametersChanged:sender];
	return self;
 }

- presetSnowshoe:sender
 {
	[notesText setText:"\n Snowshoe\n ========\n F(z) is conjugate to\n   z -> (1/z^2) + c\n\n See J. Milnor, \"Remarks on Quadratic Rational Maps\" Fig 13.\n\n\n F:1 -> -1\n"];
	[self setParameters:A_PLANE xr:-1.0 yr:0.0 xs:-2.0 ys:0.0 
		xmin:-8.2 ymin:-4.7 xmax:1.2 ymax:4.7 
		iterates:1000 cycle:30 close:0.001];
	[self parametersChanged:sender];
	return self;
 }

- presetSpyglass:sender
 {
	[notesText setText:"\n Spyglass\n ========\n"];
	[self setParameters:A_PLANE xr:-1.0 yr:0.4 xs:2.0 ys:0.0 
		xmin:-2.0 ymin:-3.0 xmax:4.0 ymax:3.0 
		iterates:50 cycle:1 close:0.01];
	[self parametersChanged:sender];
	return self;
 }

- presetWink:sender
 {
	[notesText setText:"\n Wink\n ====\n"];
	[self setParameters:A_PLANE xr:-1.0 yr:0.0 xs:1.7 ys:0.3 
		xmin:-2.0 ymin:-3.0 xmax:4.0 ymax:3.0 
		iterates:200 cycle:1 close:0.01];
	[self parametersChanged:sender];
	return self;
 }

- presetTieAndCane:sender
 {
	[notesText setText:"\n TieAndCane\n ==========\n"];
	[self setParameters:B_PLANE xr:-1.0 yr:0.0 xs:50.0 ys:200.0 
		xmin:-2.0 ymin:-2.0 xmax:2.0 ymax:2.0 
		iterates:1000 cycle:1 close:0.01];
	[self parametersChanged:sender];
	return self;
 }

- presetNapoleon:sender
 {
	[notesText setText:"\n Napoleon\n ========\n"];
	[self setParameters:A_PLANE xr:-1.0 yr:0.0 xs:3.0 ys:0.0 
		xmin:-3.0 ymin:-4.0 xmax:5.0 ymax:4.0 
		iterates:100 cycle:1 close:0.01];
	[self parametersChanged:sender];
	return self;
 }

- presetSperm:sender
 {
	[notesText setText:"\n Sperm\n =====\n"];
	[self setParameters:A_PLANE xr:0.3 yr:-0.4 xs:1.2 ys:0.3 
		xmin:-9.0 ymin:-16.0 xmax:9.0 ymax:2.0 
		iterates:200 cycle:1 close:0.1];
	[self parametersChanged:sender];
	return self;
 }

- presetRing:sender
 {
	[notesText setText:"\n Ring\n ====\n"];
	[self setParameters:B_PLANE xr:0.3 yr:-0.4 xs:1.2 ys:0.3 
		xmin:-4.6 ymin:-4.6 xmax:2.1 ymax:2.1 
		iterates:200 cycle:1 close:0.1];
	[self parametersChanged:sender];
	return self;
 }

- presetBloodClot:sender
 {
	[notesText setText:"\n BloodClot\n =========\n Same as Alien, but\nshowing the a-plane\ninstead of the\n(1/a)-plane.\n"];
	[self setParameters:A_PLANE xr:0.0 yr:0.0 xs:0.0 ys:0.0 
		xmin:-1.25 ymin:0.1 xmax:1.25 ymax:2.6 
		iterates:500 cycle:1 close:0.15];
	[self parametersChanged:sender];
	return self;
 }

- presetAlien:sender
 {
	[notesText setText:"\n Alien\n =====\n See Madelbrot Plate X and Milnor Fig 12.\n\n The actual Mandelbrot set is obtained by setting close = 0.001; see Yin p. 146.\n"];
	[self setParameters:A_INVERSE_PLANE xr:0.0 yr:0.0 xs:0.0 ys:0.0 
		xmin:-1.02 ymin:-1.02 xmax:1.02 ymax:1.02 
		iterates:1000 cycle:1 close:0.1];
	[self parametersChanged:sender];
	return self;
 }

- presetLunar:sender
 {
	[notesText setText:"\n Lunar\n =====\n Same as Alien with cycle = 10\n"];
	[self setParameters:A_INVERSE_PLANE xr:0.0 yr:0.0 xs:0.0 ys:0.0 
		xmin:-1.02 ymin:-1.02 xmax:1.02 ymax:1.02 
		iterates:1000 cycle:10 close:0.05];
	[self parametersChanged:sender];
	return self;
 }

- presetScallops:sender
 {
	[notesText setText:"\n Scallops\n ========\n See J. Milnor, \"Remarks on Quadratic Rational Maps\" Fig 11.\n\n\nF:-1 -> 0 -> inf(fixed)\n"];
	[self setParameters:A_INVERSE_PLANE xr:0.0 yr:0.0 xs:2.0 ys:0.0 
		xmin:-1.02 ymin:-1.02 xmax:1.02 ymax:1.02 
		iterates:1000 cycle:1 close:0.1];
	[self parametersChanged:sender];
	return self;
 }

- (BOOL) continue
 // Ignores all mousedown events until Stop is pressed
{
	NXEvent	*peekedEvent;
	
	peekedEvent = [NXApp peekAndGetNextEvent:NX_MOUSEDOWNMASK];
	if (peekedEvent IS NULL)
		return YES;
	else if (NXPointInRect(&(peekedEvent->location), &stopRect))
	{
		running = NO;
		return NO;
	}
	else
		return YES;
}

- drawSelf: (NXRect *)rect : (int) count
 {
	NXRect  frameRect;
	int p, q, 				// pixel coords
		pmax, qmax, 		// pixels in screen
		pstart;				// start of line to draw
	float x, y,				// actual coords of point
		dx, dy,				// distance between pixels
		oldCol, newCol,		// colours of adjacent pixels
		printerDot,			// distance between printer dots 
		lineWidth,			// width 1 on printer is PRINTER_PIXEL_FACTOR
		qHeight;			// vertical position of line to be drawn

	[self getParameters:&displayplane xr:&xr yr:&yr xs:&xs ys:&ys 
		xmin:&xmin ymin:&ymin xmax:&xmax ymax:&ymax 
		iterates:&iterates cycle:&cycle close:&close];
	sqclose = SQ(close);
	invclose = 1/sqclose;
	if (lineCompleted IS 0) NXEraseRect(&bounds);
	[self getFrame:&frameRect];
	if ((NXDrawingStatus IS NX_DRAWING) AND running)
	{
		[stopButton getFrame:&stopRect];
		pmax = (int) frameRect.size.width;
		qmax = (int) frameRect.size.height;
		dx = (xmax - xmin)/pmax;
		dy = (ymax - ymin)/qmax;
		PSsetlinewidth(1.0);
 		for (q = lineCompleted, y = ymin + lineCompleted*dy; 
			((q <= qmax) AND running); q++, y += dy)
		{
			for (p = 0, pstart = 0, x = xmin, oldCol = NX_WHITE;
							((p <= pmax) AND [self continue]); p++, x += dx)
			{
				newCol = [self pixelCol:x :y];
				if (newCol IS_NOT oldCol)
				{
					if (oldCol IS_NOT NX_WHITE)
						[self drawline:oldCol :pstart :(p-1) :q];
					pstart = p;
					oldCol = newCol;
				}
			}
			if (oldCol IS_NOT NX_WHITE)
				[self drawline:oldCol :pstart :p :q];
			[window flushWindow];
		}
		if (q < qmax)
		{
			lineCompleted = q-1;
			[startButton setTitle:"Cont"];
		}
		else
		{
			lineCompleted = 0;
			[startButton setTitle:"Start"];
		}
	}
	else if (NXDrawingStatus IS NX_PRINTING)
	{
		NXEraseRect(&bounds);
		pixelSize = 1 +  [[printerPopUpButton target]  	
								indexOfItem:[printerPopUpButton title]];
		pmax = (int) ((atoi(NXGetDefaultValue("System", "PrinterResolution")))
			 					*PRINTED_SIZE/pixelSize);
		qmax = pmax;
		lineWidth = frameRect.size.width/pmax;
		printerDot = lineWidth/pixelSize;
		dx = (xmax - xmin)/pmax;
		dy = (ymax - ymin)/qmax;
		if (pixelSize > 1)
			PSsetlinewidth(lineWidth*LINE_WIDTH_FACTOR);
		else
			PSsetlinewidth(0);
	 	for (q = 0, y = ymin; (q <= qmax); q++, y += dy)
		{
			qHeight = (q + 0.5)*lineWidth;
			for (p = 0, pstart = 0, x = xmin, oldCol = NX_WHITE; p <= pmax;
					p++, x += dx)
			{
				newCol = [self pixelCol:x :y];
				if (newCol IS_NOT oldCol)
				{
					if (oldCol IS_NOT NX_WHITE)
						[self drawline :oldCol :(pstart*lineWidth) 
										:(p*lineWidth - printerDot) :qHeight];
					pstart = p;
					oldCol = newCol;
				}
			}
			if (oldCol IS_NOT NX_WHITE)
				[self drawline:oldCol :(pstart*lineWidth) :(p*lineWidth)
											 						:qHeight];
			[window flushWindow];
		}
	}		
	return self;
 }

- (void) drawline:(float) col :(float) p1 :(float) p2 :(float) q
{
	PSnewpath();
	PSmoveto(p1, q);
	PSlineto(p2, q);
	PSsetgray(col);
	PSstroke();
}

- (float) pixelCol:(float) x :(float) y
{
	int power = 0;
	BOOL separate = YES;		// images of critical points are separate
	double x1, y1, x2, y2, xx, yy, xa = 0.0, ya = 0.0, xb = 0.0, yb = 0.0,
		x0, y0, norma, norm0, norm1, norm2;
	
	switch (displayplane) 
	{
		case A_PLANE:
			xa = x;
			ya = y;  
			xb = xr*xa - yr*ya + xs;
			yb = xr*ya + yr*xa + ys;
			break;
		case A_INVERSE_PLANE:
			norma = x*x + y*y;
			if (norma > 0.0)
			{
				xa = x/norma;
				ya = -y/norma;
			}
			xb = xr*xa - yr*ya + xs;
			yb = xr*ya + yr*xa + ys;
			break;
		case B_PLANE:
			xb = x;
			yb = y;  
			xa = xr*xb - yr*yb + xs;
			ya = xr*yb + yr*xb + ys;
			break;
	}
	norma = xa*xa + ya*ya;
	if (norma IS 0.0)
		return NX_WHITE;
	else
	{								// F(z) = (1/a)(z + 1/z + b)
		x1 = 1.0;
		y1 = 0.0;
		x2 = -1.0;
		y2 = 0.0;
		x0 = x1;
		y0 = y1;
		norm0 = x0*x0 + y0*y0;
		norm1 = x1*x1 + y1*y1;
		norm2 = x2*x2 + y2*y2;
		while (separate AND (power < iterates))
		{
			++power;
			if (norm1 IS 0.0)
			{
				x1 = INF;
				y1 = INF;	
			}
			else
			{
				xx = x1 + (x1/norm1) + xb;
				yy = y1 - (y1/norm1) + yb;
				x1 = ((xx*xa + yy*ya)/norma);
				y1 = ((yy*xa - xx*ya)/norma);
			}
			if (norm2 IS 0.0)
			{
				x2 = INF;
				y2 = INF;	
			}
			else
			{
				xx = x2 + (x2/norm2) + xb;
				yy = y2 - (y2/norm2) + yb;
				x2 = ((xx*xa + yy*ya)/norma);
				y2 = ((yy*xa - xx*ya)/norma);
			}
			if ((power MOD cycle) IS 0)
			{
				x0 = x1;
				y0 = y1;
				norm0 = x0*x0 + y0*y0;
			}
			norm1 = x1*x1 + y1*y1;
			norm2 = x2*x2 + y2*y2;
			if ((SQ(x0 - x2) + SQ(y0 - y2) < sqclose) OR 
					((norm0 > invclose) AND (norm2 > invclose)))
				separate = FALSE;
		}
		if (power < iterates)
			return NX_WHITE;
		else
			return NX_BLACK;
	}
}
 
- setParameters:(int)plane 
	xr:(float)paramsxr yr:(float)paramsyr 
	xs:(float)paramsxs ys:(float)paramsys 	
	xmin:(float)paramsxmin ymin:(float)paramsymin 
	xmax:(float)paramsxmax ymax:(float)paramsymax 
	iterates:(int)paramsiterates cycle:(int)paramscycle 
	close:(float)paramsclose	
 {
	switch (plane)
	{
		case A_PLANE:
			[planePopUpButton setTitle:
	 		" The  a-plane section, when  b = r a + s,  of"];
			break;
		case A_INVERSE_PLANE:
			[planePopUpButton setTitle:
			" The  (1/a)-plane section, when  b = r a + s,  of"];
			break;
		case B_PLANE:
			[planePopUpButton setTitle:
			" The  b-plane section, when  a = r b + s,  of"];
			break;
	}
	[paramsForm setFloatValue:paramsxr at:0];
	[paramsForm setFloatValue:paramsyr at:1];
	[paramsForm setFloatValue:paramsxs at:2];
	[paramsForm setFloatValue:paramsys at:3];
	[paramsForm setFloatValue:paramsxmin at:4];
	[paramsForm setFloatValue:paramsymin at:5];
	[paramsForm setFloatValue:paramsxmax at:6];
	[paramsForm setFloatValue:paramsymax at:7];
	[paramsForm setIntValue:paramsiterates at:8];
	[paramsForm setIntValue:paramscycle at:9];
	[paramsForm setFloatValue:paramsclose at:10];
	return self;
 }

- getParameters:(int *)plane 
	xr:(float *)paramsxr yr:(float *)paramsyr 
	xs:(float *)paramsxs  ys:(float *)paramsys 
	xmin:(float *)paramsxmin ymin:(float *)paramsymin
	xmax:(float *)paramsxmax ymax:(float *)paramsymax 
	iterates:(int *)paramsiterates cycle:(int *)paramscycle 
	close:(float *)paramsclose
 {
 	if (([paramsForm floatValueAt:0] IS_NOT xr) OR
		([paramsForm floatValueAt:1] IS_NOT yr) OR
		([paramsForm floatValueAt:2] IS_NOT xs) OR
		([paramsForm floatValueAt:3] IS_NOT ys) OR
		([paramsForm floatValueAt:4] IS_NOT xmin) OR
		([paramsForm floatValueAt:5] IS_NOT ymin) OR
		([paramsForm floatValueAt:6] IS_NOT xmax) OR
		([paramsForm floatValueAt:7] IS_NOT ymax) OR
		([paramsForm floatValueAt:8] IS_NOT iterates) OR
		([paramsForm floatValueAt:9] IS_NOT cycle) OR
		([paramsForm floatValueAt:10] IS_NOT close))
	{
		[self parametersChanged:self];
	}
	*plane = [[planePopUpButton target] indexOfItem:[planePopUpButton title]];
	*paramsxr = [paramsForm floatValueAt:0];
	*paramsyr = [paramsForm floatValueAt:1];
	*paramsxs = [paramsForm floatValueAt:2];
	*paramsys = [paramsForm floatValueAt:3];
	*paramsxmin = [paramsForm floatValueAt:4];
	*paramsymin = [paramsForm floatValueAt:5];
	*paramsxmax = [paramsForm floatValueAt:6];
	*paramsymax = [paramsForm floatValueAt:7];
	*paramsiterates = [paramsForm intValueAt:8];
	*paramscycle = [paramsForm intValueAt:9];
	*paramsclose = [paramsForm floatValueAt:10];
	return self;
 }

- parametersChanged:sender
{
	lineCompleted = 0;
	[startButton setTitle:"Start"];
	if (NXDrawingStatus IS NX_DRAWING)
	{
	  	[self lockFocus];
	    PSnewinstance();
	 	[self unlockFocus];
	}
    return self;
}

- mouseDown:(NXEvent *)theEvent
 // Draws a box on the screen to produce new view coordinates
{
  NXRect boxRect;
  NXPoint centerPoint, currentPoint;
  NXEvent *nextEvent;
  int oldMask,
  	boxSides[] = {NX_YMIN, NX_XMAX, NX_YMAX, NX_XMIN,
  				NX_YMIN, NX_XMAX, NX_YMAX, NX_XMIN};
  float aspectRatio,
  	boxGrays[] = {NX_BLACK, NX_BLACK, NX_BLACK, NX_BLACK, 
  				NX_WHITE, NX_WHITE, NX_WHITE, NX_WHITE};
  
  [window makeFirstResponder:self];
  oldMask = [window addToEventMask:NX_MOUSEDRAGGEDMASK];
  aspectRatio = bounds.size.height/bounds.size.width;
  centerPoint = theEvent->location;
  [self convertPoint:&centerPoint fromView:nil];
  NXSetRect(&boxRect,centerPoint.x,centerPoint.y,0.0,0.0);
  [self lockFocus];
  nextEvent = [NXApp getNextEvent:NX_MOUSEUPMASK | NX_MOUSEDRAGGEDMASK];
  while (nextEvent->type IS NX_MOUSEDRAGGED)
  { 
  	currentPoint = nextEvent->location;
    [self convertPoint:&currentPoint fromView:nil];
    boxRect.size.width = 2*(currentPoint.x - centerPoint.x);
    boxRect.size.height = 2*(currentPoint.y - centerPoint.y);
    if (boxRect.size.width < 0) 
		boxRect.size.width = -boxRect.size.width;
    if (boxRect.size.height < 0) 
		boxRect.size.height = -boxRect.size.height;
    if (boxRect.size.height > boxRect.size.width*aspectRatio) 
    	boxRect.size.height = boxRect.size.width*aspectRatio;
	else 
    	boxRect.size.width = boxRect.size.height/aspectRatio;
    boxRect.origin.x = centerPoint.x - .5*boxRect.size.width;
    boxRect.origin.y = centerPoint.y - .5*boxRect.size.height;
    PSnewinstance();
    PSsetinstance(YES);
	NXDrawTiledRects(&boxRect, (NXRect *)0, boxSides, boxGrays, 8);
    PSsetinstance(NO);
  	nextEvent = [NXApp getNextEvent:NX_MOUSEUPMASK | NX_MOUSEDRAGGEDMASK];
  }
  [self unlockFocus];
  [window setEventMask:oldMask];
  if ((boxRect.size.width > 0) AND (boxRect.size.height > 0)) 
  {
	[paramsForm setFloatValue:xmin + (xmax - xmin)*
		(boxRect.origin.x - bounds.origin.x)/bounds.size.width at:4];
	[paramsForm setFloatValue:ymin + (ymax - ymin)*
		(boxRect.origin.y - bounds.origin.y)/bounds.size.height at:5];
	[paramsForm setFloatValue:xmin + (xmax - xmin)*
		(boxRect.origin.x + boxRect.size.width - bounds.origin.x) 
		/bounds.size.width at:6];
	[paramsForm setFloatValue:ymin + (ymax - ymin)*
		(boxRect.origin.y + boxRect.size.height - bounds.origin.y)
		/bounds.size.height at:7];
	lineCompleted = 0;
	[startButton setTitle:"Start"];
  }
  else
  {
  	[self lockFocus];
    PSnewinstance();
 	[self unlockFocus];
  }
  return self;
}

@end
